﻿using Newtonsoft.Json;
using NSoup;
using NSoup.Nodes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Yuchen.ExtJSDTSGenerator.UI.Models;

namespace Yuchen.ExtJSDTSGenerator.UI
{
    public partial class MainForm : Form
    {
        string _jsonFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "api.json");
        List<ExtClass> _classes = null;
        Queue<ExtClass> _queue = null;
        private string _baseURL;
        public MainForm()
        {
            InitializeComponent();
            if (File.Exists(_jsonFile))
            {
                _classes = JsonConvert.DeserializeObject<List<ExtClass>>(File.ReadAllText(_jsonFile));
            }
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            NSoup.NSoupClient.Parse("html code").Select("div span a");
        }

        private async void btnGen_Click(object sender, EventArgs e)
        {
            var url = txtURL.Text.Trim();
            if (string.IsNullOrWhiteSpace(url))
            {
                return;
            }
            var uri = new Uri(url);
            _baseURL = uri.Scheme + "://" + uri.Host + "/extjs/6.0.1-classic/";
            try
            {
                await Task.Factory.StartNew(Worker, url);
            }
            catch (Exception ex)
            {
                AppendMessage(ex.ToString());
            }
        }

        ExtModule ParseMember(Node node, List<string> moduleNames)
        {
            var childrens = node.ChildNodes[3].ChildNodes.Where(x => x.Attr("class").EndsWith(" tree-member")).Cast<Element>();
            ExtModule extModule = new ExtModule();
            extModule.Name = string.Join(".", moduleNames); ;
            foreach (var children in childrens)
            {
                var item = children.Children[0];
                var name = item.Select("a").Text;
                if (item.Attr("isleaf") == "false")
                {
                    moduleNames.Add(name);
                    extModule.Modules.Add(ParseMember(children, moduleNames));
                    moduleNames.RemoveAt(moduleNames.Count - 1);
                }
                else
                {
                    var extClass = new ExtClass();
                    extClass.Name = name;
                    extClass.Module = extModule;
                    extModule.Classes.Add(extClass);
                    _classes.Add(extClass);
                }
            }
            return extModule;
        }

        private void Worker(object state)
        {
            if (_classes == null)
            {
                _classes = new List<ExtClass>();
                var url = state.ToString();
                WebClient wc = new WebClient();
                wc.Encoding = Encoding.UTF8;
                var code = string.Empty;
                AppendMessage("打开:" + url);
                code = wc.DownloadString(url);
                wc.Dispose();
                var doc = NSoupClient.Parse(code);
                var tree = doc.Select("#tree")[0];
                var extMember = tree.ChildNodes.Where(x => x.Attr("class").Contains("tree-member")).FirstOrDefault();
                var moduleNames = new List<string>();
                moduleNames.Add("Ext");
                AppendMessage("获取所有类与模块");
                _classes.Add(new ExtClass()
                {
                    Name = "Ext"
                });
                ParseMember(extMember, moduleNames);
                _queue = new Queue<ExtClass>(_classes);
                var tasks = new List<Task>();
                for (int i = 0; i < (int)numThreadCount.Value; i++)
                {
                    tasks.Add(Task.Factory.StartNew(ParseClass));
                }
                Task.WaitAll(tasks.ToArray());
                //Parallel.ForEach(_classes, new ParallelOptions()
                //{
                //    MaxDegreeOfParallelism = (int)numThreadCount.Value
                //}, ParseClass);
                File.WriteAllText(_jsonFile, JsonConvert.SerializeObject(_classes));
            }
            var file = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ExtJS-6.0.1.d.ts");
            if (File.Exists(file))
            {
                File.Delete(file);
            }
            var writer = File.AppendText(file);
            Gen(writer);
            writer.Flush();
            writer.Dispose();
            this.Invoke(new Action(() =>
            {
                MessageBox.Show(this, "完成");
            }));
        }

        void Gen(StreamWriter writer)
        {
            var modules = _classes.GroupBy(x => x.ModuleName);
            foreach (var module in modules)
            {
                if (string.IsNullOrWhiteSpace(module.Key))
                {
                    foreach (var cls in module)
                    {
                        GenClass(string.Empty, writer, cls);
                    }
                    continue;
                }
                writer.Write("declare module " + module.Key);
                writer.Write("{");
                foreach (var cls in module)
                {
                    GenClass("\n", writer, cls);
                }
                writer.WriteLine("}");
                writer.Flush();
            }
        }

        void GenClass(string tab, StreamWriter writer, ExtClass cls)
        {
            writer.Write(tab);
            writer.Write("{0} class " + cls.Name, tab == string.Empty ? "declare" : "export");
            if (cls.Extend != null)
            {
                writer.Write(" extends " + cls.Extend);
            }
            writer.WriteLine("{");
            foreach (var property in cls.Properties)
            {
                writer.Write(tab);
                writer.WriteLine("\t{0}:{1};", property.Name, property.Type);
            }
            foreach (var method in cls.Methods)
            {
                writer.Write(tab);
                writer.WriteLine("\t{0}", method.ToString());
            }
            writer.Write(tab);
            writer.WriteLine("}");
            writer.Flush();
        }

        void AppendMessage(string msg)
        {
            this.Invoke(new Action(() =>
            {
                txtLog.AppendText(DateTime.Now.ToString() + " " + msg);
                txtLog.AppendText("\n");
            }));
        }

        private void ParseClass()
        {
            WebClient wc = new WebClient();
            wc.Encoding = Encoding.UTF8;
            while (true)
            {
                ExtClass c = null;
                lock (_queue)
                {
                    if (_queue.Count > 0)
                    {
                        c = _queue.Dequeue();
                    }
                }
                if (c == null)
                {
                    break;
                }
                var code = string.Empty;
                AppendMessage("解析" + c.FullName);
                var url = _baseURL + c.FullName + ".html";
                AppendMessage("打开" + url);
                while (true)
                {
                    try
                    {
                        code = wc.DownloadString(url);
                        break;
                    }
                    catch (Exception ex)
                    {
                        AppendMessage("打开" + url + "失败，一秒后重复:" + ex.Message);
                        Thread.Sleep(1000);
                    }
                }
                wc.Dispose();
                var doc = NSoupClient.Parse(code);
                var extendEl = doc.Select(".classMetaColumn .hierarchy").Last?.PreviousElementSibling;
                if (extendEl != null)
                {
                    c.Extend = extendEl.Text();
                }
                var members = doc.GetElementsByAttributeValueStarting("id", "property-");
                foreach (var member in members)
                {
                    var styles = member.Attr("class");
                    if (styles.Contains(" isInherited "))
                    {
                        continue;
                    }
                    if (!styles.Contains(" member-public "))
                    {
                        continue;
                    }
                    var property = new ExtProperty();
                    property.Name = member.Attr("data-member-name");
                    property.IsStatic = styles.Contains(" isStatic ");
                    property.Type = member.Select(".member-header .memberType").Text;
                    if (c.Name == "Ext")
                    {
                        if (property.Name == "enableGarbageCollector")
                        {
                            property.Type = "Boolean";
                        }
                        else if (property.Name == "emptyString")
                        {
                            property.Type = "String";
                        }
                        else if (property.Name == "isDebugEnabled")
                        {
                            continue;
                        }
                        else if (property.Name == "frameStartTime")
                        {
                            property.Type = "Date";
                        }
                        else if (property.Name == "enableListenerCollection")
                        {
                            property.Type = "Boolean";
                        }
                    }
                    var memberHeader = member.Select("h2.member-header")[0];
                    if (memberHeader.NextElementSibling != null)
                    {
                        property.Description = memberHeader.NextElementSibling.Text();
                    }
                    c.Properties.Add(property);
                }
                members = doc.GetElementsByAttributeValueStarting("id", "method-");
                foreach (var member in members)
                {
                    var styles = member.Attr("class");
                    if (styles.Contains(" isInherited "))
                    {
                        continue;
                    }
                    if (!styles.Contains(" member-public "))
                    {
                        continue;
                    }
                    var method = new ExtMethod();
                    method.Name = member.Attr("data-member-name");
                    method.IsStatic = styles.Contains(" isStatic ");
                    method.Type = member.Select(".member-header memberType").Text;
                    var memberHeader = member.Select("h2.member-header")[0];
                    if (memberHeader.NextElementSibling != null)
                    {
                        method.Description = memberHeader.NextElementSibling.Text();
                    }
                    var parameters = member.Select("div.param-row");
                    foreach (var parameter in parameters)
                    {
                        var parameterInfo = new ExtParameter();
                        parameterInfo.IsOptional = parameter.Select("i.memberType").Count > 0;
                        parameterInfo.Name = parameter.GetElementsByAttributeValue("class", "params-list param-list-item").Text;
                        parameterInfo.Type = parameter.Select("span.memberType").Text;
                        parameterInfo.Description = parameter.Select(".params-desc").Text;
                        method.Parameters.Add(parameterInfo);
                    }
                    c.Methods.Add(method);
                }

                members = doc.GetElementsByAttributeValueStarting("id", "cfg-");
                foreach (var member in members)
                {
                    var styles = member.Attr("class");
                    if (styles.Contains(" isInherited "))
                    {
                        continue;
                    }
                    if (!styles.Contains(" member-public "))
                    {
                        continue;
                    }
                    var property = new ExtProperty();
                    property.Name = member.Attr("data-member-name");
                    property.IsStatic = styles.Contains(" isStatic ");
                    property.Type = member.Select(".member-header .memberType").Text;
                    var memberHeader = member.Select("h2.member-header")[0];
                    if (memberHeader.NextElementSibling != null)
                    {
                        property.Description = memberHeader.NextElementSibling.Text();
                    }
                    c.Configs.Add(property);
                }
                members = doc.GetElementsByAttributeValueStarting("id", "event-");
                foreach (var member in members)
                {
                    var styles = member.Attr("class");
                    if (styles.Contains(" isInherited "))
                    {
                        continue;
                    }
                    if (!styles.Contains(" member-public ") && !styles.Contains(" member-protected "))
                    {
                        continue;
                    }
                    var extEvent = new ExtEvent();
                    extEvent.Name = member.Attr("data-member-name");
                    var memberHeader = member.Select("h2.member-header")[0];
                    if (memberHeader.NextElementSibling != null)
                    {
                        extEvent.Description = memberHeader.NextElementSibling.Text();
                    }
                    var parameters = member.Select("div.param-row");
                    foreach (var parameter in parameters)
                    {
                        var parameterInfo = new ExtParameter();
                        parameterInfo.Name = parameter.GetElementsByAttributeValue("class", "params-list param-list-item").Text;
                        parameterInfo.Type = parameter.Select("span.memberType").Text;
                        parameterInfo.Description = parameter.Select(".params-desc").Text;
                        extEvent.Parameters.Add(parameterInfo);
                    }
                    c.Events.Add(extEvent);
                }
            }
        }
    }
}
